package XMFramework.Core
{
	import GTweener.TweenLite;
	
	import Interfaces.MIAppStarter;
	
	import XMFramework.Core.Events.SEvent;
	import XMFramework.Interface.Architechture.MVCLayerView;
	import XMFramework.Interface.Architechture.SMController;
	import XMFramework.Interface.Architechture.SMModel;
	import XMFramework.Interface.Architechture.SMView;
	import XMFramework.Interface.IConfig;
	import XMFramework.Interface.IEventDispatcher;
	import XMFramework.Interface.IFramework;
	import XMFramework.Interface.IGlobalHolder;
	import XMFramework.Interface.ILocalization;
	import XMFramework.Interface.IModule;
	import XMFramework.Interface.INetwork;
	import XMFramework.Interface.IResourcesManager;
	import XMFramework.Interface.IRuntimeUpdater;
	import XMFramework.Interface.ISoundFX;
	import XMFramework.Interface.ITimer;
	
	import flash.display.BitmapData;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;

	public class SCore implements IFramework
	{		
		//////////////////////////
		// Constant
		public static const SUCCESS : int							=	0;	
		public static const START_UP : int							=	1;
		public static const RESOURCE_LOAD : int						=	2;
		public static const RESOURCE_LOAD_COMPLETE : int			=	3;
		public static const INIT_MODULE : int						=	4;
		
		public static const ERROR : int							=	-1;
		public static const ERROR_CONFIG_URL : int				=	-2;
		public static const ERROR_LOAD_LOCALIZATION : int		=	-3;
		public static const ERROR_LOAD_MODULE : int				=	-4;
		public static const ERROR_INIT_MODULE : int				=	-5;
		
		// Use by SNetwork
		public static const ERROR_SERVER_NULL : int				=	-6;
		public static const ERROR_SERVER_ERROR : int			=	-7;
		
		//////////////////////////
		// Pointer to framework component
		private var hostObject:MIAppStarter;
		private var configPtr:SConfig;
		private var moduleDomainPtr:SModuleDomain;
		private var networkPtr:SNetwork;
		private var resourceLoaderPtr:SResourceManager;
		private var runtimeUpdaterPtr:SRuntimeUpdater;
		private var globalHolderPtr:SGlobalHolder;
		private var timerPtr:STimer;
		private var localizerPtr:SLocalization;
		private var soundFXPtr:SSoundFX;
		private var eventDispatcherPtr:SEventDispatcher; 
		
		//////////////////////////
		// System stage
		private var initializeStage:int			=	0;
		
		//////////////////////////
		// System start up params		
		private var mediaPath:String			=	"";
		private var requireAssets:Array			=	null;
		private var hostPath:String				=	"";
		
		//////////////////////////
		// MVC module architechture		
		private var controllerArch:Object		=	new Object();
		private var modelsArch:Object			=	new Object();
		private var viewsArch:Object			=	new Object();
		
		//////////////////////////	
		// For MVC view functional
		private var doInvalidateView:Boolean	=	true;
		private var renderBitmap:BitmapData		=	null;	
		
		///////////////////////////////////////////////////////////////
		// public function
		
		public function SCore()
		{
			initializeStage	=	0;
			
			eventDispatcherPtr	=	new SEventDispatcher();
			eventDispatcherPtr.directAddEventListener( "SCore_Broadcasting", dispatchBroadcateData )
		}
		
		//////////////////////////
		// Functional method 
		
		// onResult callback function when setup complete <i>onResult( errCode : int, errMsg : String )</i>
		public function start( host:MIAppStarter, hostUrl:String, mediaUrl:String, configFileUrl:String, configMeta:Array, patchFileUrl:String, requireAssetsArr:Array, onResult:Function ) : void
		{
			trace( "SCore::start starting system" );
			
			if( ! host )
			{
				trace( "SCore::start host container can't be null" );
				onResult( ERROR, "stageObject is NULL" );
				return;
			}
			
			hostObject	=	host;
			
			if( hostObject.getWidth() <= 0 || hostObject.getHeight() <= 0 )
			{
				trace( "SCore::start host container size invalid" );
				onResult( ERROR, "Host container size width = 0 or height = 0" );
				return;
			}
			
			mediaPath		=	mediaUrl;
			requireAssets	=	requireAssetsArr;
			hostPath		=	hostUrl;			
			
			// Starting download config system to run engine			
			loadConfigSystem( configFileUrl, configMeta, patchFileUrl, onResult );
		}		
		
		// Send a call to all module if specify <b>target</b> array or to all module if NULL 
		public function broadcasting( target : Array, channelId : String, ...params ) : void
		{
			eventDispatcherPtr.dispatchEvent( "SCore_Broadcasting", false, false, target, channelId, params );
		}
		
		private function dispatchBroadcateData( event:SEvent ):void
		{
			var module : IModule;
			
			var targetMdl:Array		=	event.params[0];
			var channelId:String	=	event.params[1];
			var paramsArr:Array		=	event.params[2];
			
			// Filter specify command name
			if( targetMdl )
			{
				for( var i:String in targetMdl )
				{
					for( var j:String in moduleDomainPtr.modulesList )
					{
						if( moduleDomainPtr.modulesList[i].handle == targetMdl[i] )
						{
							module	=	moduleDomainPtr.modulesList[targetMdl[i]].handle as IModule;
							module.onBroadcasting.apply( 0, [targetMdl, channelId].concat(paramsArr) );
						}
					}
				}
			}
				
				// Choose all command architechture
			else
			{
				for( i in moduleDomainPtr.modulesList )
				{
					module	=	moduleDomainPtr.modulesList[i].handle as IModule;
					module.onBroadcasting.apply( 0, [targetMdl, channelId].concat(paramsArr) );
				}	
			}
			
			//
			eventDispatcherPtr.dispatchEvent.apply( null, [channelId, false, false].concat( paramsArr ) );
		}		
		
		//////////////////////////
		// System stage methods
		
		public function getStage():Stage
		{
			return hostObject.getHostContainer().stage;
		}
		
		public function isInitCompleted() : Boolean
		{
			return initializeStage == 2;	
		}
				
		// Return pointer to top level application 
		public function getRootObject() : MIAppStarter
		{
			return hostObject;
		}
		
		///////////////////////////////////////////////////////////////
		// Interface methods
		
		public function get networkSystem():INetwork
		{
			return networkPtr;
		}
		
		public function get resourceManager():IResourcesManager
		{
			return resourceLoaderPtr;
		}
		
		public function get runtimeUpdater():IRuntimeUpdater
		{
			return runtimeUpdaterPtr;
		}
		
		public function get globalHolder():IGlobalHolder
		{
			return globalHolderPtr;
		}
		
		public function get configData():IConfig
		{
			return configPtr;
		}
		
		public function get timer():ITimer
		{
			return timerPtr;
		}
		
		public function get localizer():ILocalization
		{
			return localizerPtr;
		}
		
		public function get soundFX():ISoundFX
		{
			return soundFXPtr;
		}
		
		public function get eventDispatcher():IEventDispatcher
		{
			return eventDispatcherPtr;
		}
		
		public function getArchCommand( archName : String ) : SMController
		{
			if( controllerArch.hasOwnProperty( archName ) )
				return controllerArch[archName];
			
			trace( "SCore ERROR: get command name:'" + archName + "' is not found." );
			return null;
		}
		
		public function getArchModel( archName : String ) : SMModel
		{
			if( modelsArch.hasOwnProperty( archName ) )
				return modelsArch[archName];
			
			trace( "SCore ERROR: get model name:'" + archName + "' is not found." );
			return null;
		}
		
		public function getArchView( archName : String ) : SMView
		{
			if( viewsArch.hasOwnProperty( archName ) )
				return viewsArch[archName];
			
			trace( "SCore ERROR: get view name:'" + archName + "' is not found." );
			return null;
		}
		
		public function registerArchitechture( ownerModule:IModule, archName:String, controller:SMController, model:SMModel, view:SMView ) : void
		{
			if( controller )
			{
				controller.coreSNSFramework	=	this;
				controller.viewLayer			=	view;
				controller.modelLayer			=	model;
				controller.modulePtr			=	ownerModule;
				
				if( controllerArch.hasOwnProperty( archName ) )
					trace( "SCore ERROR: Register command layer id='" + archName + "' has already existed." );
				
				controllerArch[archName]		=	controller;
			}
			
			if( model )
			{
				model.coreSNSFramework		=	this;
				model.viewLayer				=	view;
				model.commandLayer			=	controller;
				model.modulePtr				=	ownerModule;
				
				if( modelsArch.hasOwnProperty( archName ) )
					trace( "SCore ERROR: Register model layer id='" + archName + "' has already existed." );
				
				modelsArch[archName]		=	model;
			}
			
			if( view )
			{
				view.coreSNSFramework		=	this;
				view.modelLayer				=	model;
				view.commandLayer			=	controller;
				view.modulePtr				=	ownerModule;
				
				if( viewsArch.hasOwnProperty( archName ) )
					trace( "SCore ERROR: Register view layer id='" + archName + "' has already existed." );
				
				viewsArch[archName]		=	view;
			}
			
			// Signal register complete event to assign collarate framework tire for each component
			if( controller )
				controller.onRegisterCompleted();
			
			if( model )
				model.onRegisterCompleted();
			
			if( view )
				view.onRegisterCompleted();
		}
		
		// Add form to display list if it not exist. Or remove form from display list if it exist.		
		public function toggleView( name : String ) : SMView
		{
			if( viewsArch.hasOwnProperty( name ) )
			{
				use namespace MVCLayerView;
				
				var v : SMView	=	viewsArch[name];
				
				if( v.parentContainer )
				{
					// Remove from render list
					if( v.parentContainer.contains( v ) )					
					{
						v.onRemoveFromParent( v.parentContainer );
						
						// Signal callback event
						v.onHide();
					}
						
						// Now add to display list
					else
					{
						v.onAddToParent( v.parentContainer );
						
						// Re align form to correct position
						//alignView( viewData[viewId].form );
						
						// Signal callback event
						v.onShow();
					}
					
					invalidate();
				}
				
				return v;
			}
				
			else
			{
				trace( "SCore: toggleView name: " + name + " not found." );
				return null;
			}
		}
		
		// Add form to display list 
		public function showView( name : String ) : SMView
		{		
			if( viewsArch.hasOwnProperty( name ) )
			{
				use namespace MVCLayerView;
				
				var v : SMView	=	viewsArch[name];
				
				if( v.parentContainer )
				{		
					v.onAddToParent( v.parentContainer );
					
					// Signal callback event
					v.onShow();				
					
					invalidate();
				}
				else
					trace( "SCore: showView name: " + name + ". Parent container is not set before." );
				
				return v;
			}
				
			else
			{
				trace( "SCore: showView name: " + name + " not found." );
				return null;
			}			
		}
		
		// Remove form to display list 
		public function hideView( name : String ) : SMView
		{
			if( viewsArch.hasOwnProperty( name ) )
			{
				use namespace MVCLayerView;
				
				var v : SMView	=	viewsArch[name];
				
				v.onRemoveFromParent( v.parentContainer );
				
				// Signal callback event
				v.onHide();				
				
				invalidate();
				
				return v;
			}
				
			else
			{
				trace( "SCore: hideView name: " + name + " not found." );
				return null
			}
		}
		
		// Return true if hit any form 
		public function viewHitTestPoint( x:int, y:int ) : Boolean
		{
			if( hostObject )
			{
				if( doInvalidateView )
				{	
					if( renderBitmap )
					{
						renderBitmap.dispose();
						renderBitmap	=	null;
					}
					
					renderBitmap	=	new BitmapData( hostObject.getWidth(), hostObject.getHeight(), true, 0x00000000 );
					renderBitmap.fillRect( new Rectangle( 0, 0, renderBitmap.width, renderBitmap.height ), 0x00000000 );
					doInvalidateView	=	false;
				}
				
				var uiContainers : Array	=	hostObject.getUIContainer();
				for( var i : String in uiContainers )
					renderBitmap.draw( uiContainers[i] );
								
				return renderBitmap.hitTest( new Point( 0, 0 ), 0x20, new Point( x, y ) ) ;;
			}
			
			return false;
		}		
		
		public function invalidate() : void
		{
			// Listener event to rebuild view hit test bitmap only when mouse up
			// Without this issue an error that stage continue progress mouse event after it dispached to other view component
			//
			// This issue can sovle by calling function Event.stopPropagation() or Event.stopImmediatePropagation()
			// But by this way make inconvinence for developer
			
			// For asyn call
			GTweener.TweenLite.delayedCall( 1, allowClearView, null, true );
		}		
		
		///////////////////////////////////////////////////////////////
		// private function
		
		private function allowClearView() : void
		{
			doInvalidateView	=	true;
		}
		
		private function onUpdateFrame( event : Event ) : void
		{
			if( timerPtr )
				STimer(timerPtr).updateTimer();
			
			if( networkPtr )
				SNetwork(networkPtr).updateFrame( timerPtr.deltaTime, timerPtr.totalTime ); 
			
			if( runtimeUpdaterPtr )
				runtimeUpdaterPtr.onFrameUpdate( timerPtr.deltaTime, timerPtr.totalTime );
		}
		
		private function calAssetTotalByte():int
		{
			var totalByte:int	=	0;
			
			if( configPtr )
				totalByte	+=	configPtr.totalBytes;			
			
			if( resourceLoaderPtr )
				totalByte	+=	resourceLoaderPtr.calResourcesTotalByte();
			
			if( localizerPtr )
				totalByte	+=	localizerPtr.totalByte;
			
			if( moduleDomainPtr )
				totalByte	+=	moduleDomainPtr.calResourcesTotalByte();
			
			if( totalByte == 0 )
				return int.MAX_VALUE;
			else if( totalByte < 2097152 ) // Assume 2M size
				totalByte	=	2097152;
			
			return totalByte;
		}
		
		private function calAssetLoadedByte():int
		{
			var loadedByte:int	=	0;
			
			if( configPtr )
				loadedByte	+=	configPtr.loadedBytes;	
			
			if( resourceLoaderPtr )
				loadedByte	+=	resourceLoaderPtr.calResourcesLoadedByte();
			
			if( localizerPtr )
				loadedByte	+=	localizerPtr.loadedByte;
			
			if( moduleDomainPtr )
				loadedByte	+=	moduleDomainPtr.calResourcesLoadedByte();
			
			return loadedByte;
		}
		
		private function loadConfigSystem( configFileUrl:String, configMeta:Array, patchFileUrl:String, onResult:Function ):void
		{
			trace( "SCore::loadConfigSystem started" );
			
			configPtr		=	new SConfig();
			
			if( patchFileUrl != null && patchFileUrl.length != 0 )
				configPtr.loadPatchFile( patchFileUrl, onLoadPatchFile, onLoadedConfigProgress );
				
			else
			{
				//configPtr.loadSWF( configFileUrl, configMeta, onConfigResult, onLoadedConfigProgress );
				
				for( var i:int = 0; i < configMeta.length; ++i )
				{
					configMeta[i] = configFileUrl + configMeta[i];
				}
				
				configPtr.loadJSON( configMeta, onConfigResult );
			}
			
			function onLoadPatchFile( code:int, message:String ):void
			{
				if( code >= 0 )
				{
					configPtr.loadSWF( configFileUrl, configMeta, onConfigResult, onLoadedConfigProgress );
				}					
				else // Load failed
				{
					throw new SError( SError.ERR_JSON_NOT_FOUND, null, patchFileUrl );
					onResult( ERROR_CONFIG_URL, message );
				}
			}
			
			function onConfigResult( code:int, message:String ) : void
			{
				// Load JSON succeeded
				if( code >= 0 )
				{
					trace( "SCore::loadConfigSystem succeeded" );
					
					// Continue system start up progress
					initSystemComponent( onResult );
					
				}				
				else // Load failed
				{
					throw new SError( SError.ERR_JSON_NOT_FOUND, null, configFileUrl );
					onResult( ERROR_CONFIG_URL, message );
				}
			}
			
			function onLoadedConfigProgress( evt:ProgressEvent ) : void
			{
				configPtr.totalBytes	=	evt.bytesTotal;
				configPtr.loadedBytes	=	evt.bytesLoaded;
				onResult( RESOURCE_LOAD, null, 100, 1, "configuration", evt.bytesTotal, evt.bytesLoaded );
			}
		}
		
		private function initSystemComponent( onResult:Function ) : void
		{
			trace( "SCore::initSystemComponent started" );
			
			// First init timer object
			timerPtr				=	new STimer();
			STimer(timerPtr).initTimer();
			
			// Initialize updater system
			runtimeUpdaterPtr		=	new SRuntimeUpdater();
			
			// Initilize network system			
			networkPtr				=	new SNetwork( hostObject );
			
			// Initilize global framework storage system
			globalHolderPtr			=	new SGlobalHolder();
			
			// Initilize resource system
			resourceLoaderPtr		=	new SResourceManager();
			resourceLoaderPtr.startLoadResources( 
				mediaPath, 
				configPtr.getMetadata( ShareMacros.CONF_SWF_MEDIA ), 
				configPtr.getMetadata( ShareMacros.CONF_SWF_PATCHFILES ),
				requireAssets,
				onResourceStepLoaded, onResLoadComplete );			
			
			// Initialize localization system			
			//localizerPtr			=	new SLocalization( mediaPath, configPtr.getMetadata( ShareMacros.CONF_LOCALIZATION ) );
			//localizerPtr.downloadData( onResLoadComplete, onResourceStepLoaded );
			
			// Init sound effect manager system
			//soundFXPtr				=	new SSoundFX();
			//var soundData:Object	=	configPtr.getMetadata( ShareMacros.CONF_SOUNDS );
			//soundFXPtr.loadConfigData( mediaPath, soundData );
			
			// Init MVC module list
			moduleDomainPtr		=	new SModuleDomain();
			moduleDomainPtr.loadModuleList( hostPath, configPtr.getMetadata( ShareMacros.CONF_SNS_COMPONENT ), onResult, onResourceStepLoaded, onResLoadComplete );
			
			// Tell them we start up to initialize system
			onResult( START_UP, null );
			
			function onResourceStepLoaded( bucket : String, totalByte:uint, loadedByte:uint ) : void
			{
				onResult( RESOURCE_LOAD, null, calAssetTotalByte(), calAssetLoadedByte(), bucket, totalByte, loadedByte );
			}
			
			function onResLoadComplete() : void
			{
				onResult( RESOURCE_LOAD_COMPLETE, null );
				if( moduleDomainPtr.isInitComplete )
				{
					if( initializeStage == 0 ) 
					{
						initializeStage	=	1;
						initMVCSystem( onResult );						
					}					
				}
				
				/*if( localizerPtr.isInitCompleted &&
					resourceLoaderPtr.isInitCompleted &&
					moduleDomainPtr.isInitComplete )
				{					
					// All media asset is loaded completely
					onResult( RESOURCE_LOAD_COMPLETE, null );
					
					// Building MVC system
					// Note: 
					// function 'onResLoadComplete' can be call multi time by event dispatcher
					// use 'initializeStage' variable to detect 'initMVCSystem' is called only one time
					if( initializeStage == 0 ) 
					{
						initializeStage	=	1;
						initMVCSystem( onResult );						
					}
				}*/			
			}
		}

		private function initMVCSystem( onResult:Function ):void
		{			
			trace( "SCore::initMVCSystem started" );
			
			// Signal callback onStart to new module instance
			// This step will take a lot of time
			var loadMldCount:int	=	1;
			var scorePtr:IFramework	=	this;				
			
			// Just wait for next frame
			GTweener.TweenLite.delayedCall( 1, startModule, [0], true );
			
			function startModule( index:int ):void
			{
				onResult( INIT_MODULE, null, moduleDomainPtr.modulesList.length, loadMldCount );
				loadMldCount++;
				
				var mdl:IModule		=	moduleDomainPtr.modulesList[index].handle as IModule;
				
				mdl.frameworkPtr	=	scorePtr;
				mdl.onFrameworkStart();				
				
				// Next
				completeModule( index+1 );
			}
			
			function completeModule( index:int ):void
			{
				if( index >= moduleDomainPtr.modulesList.length )
				{
					// Register listen to frame update event
					hostObject.getHostContainer().addEventListener( Event.ENTER_FRAME, onUpdateFrame );
					
					// Framework start completed
					initializeStage	=	2;
					
					//CommomRes.init( scorePtr );					
					
					// Framework start completed
					onResult( SUCCESS, "Framework start successfully" );
					
					// Release all resource
					mediaPath		=	null;
					hostPath		=	null;
					requireAssets	=	null;
					
					// Background loading 'afterload' type asset
					resourceLoaderPtr.backgroundLoading();
					
					trace( "Framework start successfully-----------------------------------------------------" );
				}
				else
					GTweener.TweenLite.delayedCall( 1, startModule, [index], true );
			}
		}
	}
}